#region Usings

using System;
using System.Collections.Generic;
using System.Linq;
using System.Reflection;
using log4net;
using Karma.Framework.Core.Domain.Tools.Import.Data;
using Karma.Framework.Core.Domain.Tools.Import.Layout;

#endregion

namespace Karma.Framework.Core.Domain.Tools.Import.Validation
{
    public class DefaultDataValidator : IDataValidator
    {
        private static readonly ILog LOG = LogManager.GetLogger(typeof (DefaultDataValidator).Name);

        #region IDataValidator Members

        public ValidationSummary Validate(LayoutData layoutData)
        {
            if (LOG.IsDebugEnabled)
            {
                LOG.Debug(string.Format("Validating Data for entity {0}", layoutData.Layout.Entity.FullName));
            }
            var summary = new ValidationSummary();
            var errors = ValidateEntity(layoutData);
            if (layoutData.Aggregates != null)
            {
                foreach (var agregado in layoutData.Aggregates)
                {
                    var errorsAgregado = ValidateEntity(agregado);
                    if (errorsAgregado != null)
                    {
                        errors.Errors = errors.Errors.Concat(errorsAgregado.Errors).ToList();
                    }

                    var references = (from row in agregado.Data
                                      let cells = row.Data
                                      from cell in cells
                                      where cell.Definition is ReferenceField &&
                                            cell.Definition.FieldType.Equals(layoutData.Layout.Entity)
                                      select new {Cell = cell, Row = row});
                    foreach (var cellData in references)
                    {
                        var reference = (ReferenceField) cellData.Cell.Definition;
                        var refPropName = reference.KeyRef ?? "Id";

                        if (((from row in layoutData.Data
                              let cells = row.Data
                              from cell in cells
                              where cell.Definition.Name == refPropName &&
                                    cell.Value.Equals(cellData.Cell.Value)
                              select row).Count() == 0))
                        {
                            AddErrorToSummary(ref errors, cellData.Row.Row, cellData.Cell.Column,
                                              cellData.Cell.Definition, string.Format(
                                                                            "El valor de referencia {0} no se encuentra.",
                                                                            cellData.Cell.Value));
                        }
                    }
                }
            }
            if (errors != null)
            {
                summary.ErrorSummary = errors;
            }
            if (LOG.IsDebugEnabled)
            {
                LOG.Debug("Data validated.");
            }
            return summary;
        }

        #endregion

        private ErrorSummary ValidateEntity(LayoutData layoutData)
        {
            ErrorSummary summary = null;
            if (LOG.IsDebugEnabled)
            {
                LOG.Debug(string.Format("Records to be validated: [{0}]", layoutData.Data.Count));
            }
            for (var i = 0; i < layoutData.Data.Count; i++)
            {
                if (LOG.IsDebugEnabled && i > 0 && i % 100 == 0)
                {
                    LOG.Debug(string.Format("{0} records validated.", i));
                }
                var rowData = layoutData.Data[i];
                // primary key
                if (rowData.PrimaryKey != null && !string.IsNullOrEmpty((string)rowData.PrimaryKey.Value))
                {
                    int intValue;
                    if (!int.TryParse(rowData.PrimaryKey.Value.ToString(), out intValue))
                    {
                        AddErrorToSummary(ref summary, i + 1, 1, rowData.PrimaryKey.Definition,
                                          rowData.PrimaryKey.Value);
                    }
                }
                // properties
                for (var j = 0; j < rowData.Data.Count; j++)
                {
                    var data = rowData.Data[j];
                    /* simple type field*/
                    if (data.Definition is PropertyField)
                    {
                        var field = (Property) data.Definition;
                        // value type
                        if (field.FieldType.IsValueType &&
                            !field.FieldType.Equals(typeof (DateTime)) &&
                            !string.IsNullOrEmpty((string)data.Value) &&
                            !field.FieldType.IsEnum)
                        {
                            object fieldValue;
                            if (!TryParse(field.FieldType, (string) data.Value,
                                          out fieldValue, field))
                            {
                                AddErrorToSummary(ref summary, i + 1, j + 1, field, data.Value);
                            }
                        }
                        // date time
                        if (field.FieldType.Equals(typeof(DateTime)))
                        {
                            int intValue;
                            if (int.TryParse(data.Value.ToString(), out intValue))
                            {
                                try
                                {
                                    new DateTime(1899, 12, 31).AddDays(intValue);
                                }
                                catch
                                {
                                    AddErrorToSummary(ref summary, i + 1, j + 1, field, data.Value);
                                }
                            }
                            else
                            {
                                AddErrorToSummary(ref summary, i + 1, j + 1, field, data.Value);
                            }
                        }
                        // enum
                        if (field.FieldType.IsEnum)
                        {
                            if (!field.Nullable && data.Value == null)
                            {
                                AddErrorToSummary(ref summary, i + 1, j + 1, field, data.Value);
                            }
                            else
                            if (!field.Nullable && data.Value != null && !Enum.IsDefined(field.FieldType, data.Value))
                            {
                                AddErrorToSummary(ref summary, i + 1, j + 1, field, data.Value);
                            }
                        }
                        // string
                        if (field.FieldType.Equals(typeof(string)) && field.Size > 0)
                        {
                            if (data.Value.ToString().Length > field.Size)
                            {
                                AddErrorToSummary(ref summary, i + 1, j + 1, field, data.Value);
                            }
                        }
                    }
                    /* asossiation type field*/
                    if (data.Definition is ReferenceField)
                    {
                        var reference = (ReferenceField) data.Definition;
                        if (data.Value == null)
                        {
                            if (!reference.Nullable)
                            {
                                AddErrorToSummary(ref summary, i + 1, j + 1, reference, data.Value);
                            }
                        }
                        else
                        {
                            if (LOG.IsDebugEnabled)
                            {
                                LOG.Debug(string.Format("Validating Field: [{0}] of Type [{1}], Reference: [{2}], ReferenceType: [{3}]",
                                    reference.Name, layoutData.Layout.Entity.Name, reference.KeyRef, reference.KeyRefType != null? reference.KeyRefType.FullName: "_"));
                            }
                            if (reference.KeyRefType != typeof(string) && reference.KeyRefType != typeof(int))
                            {
                                object val;
                                if (!TryParse(reference.KeyRefType, data.Value.ToString(), out val, reference))
                                {
                                    AddErrorToSummary(ref summary, i + 1, j + 1, reference, data.Value);
                                }
                            }
                            else
                            {
                                if (reference.KeyRefType == typeof(int))
                                {
                                    int val;
                                    if (!int.TryParse(data.Value.ToString(), out val))
                                    {
                                        AddErrorToSummary(ref summary, i + 1, j + 1, reference, data.Value);
                                    }
                                }
                            }
                        }
                    }
                }
            }
            if (LOG.IsDebugEnabled)
            {
                LOG.Debug(string.Format("[{0}] Records validated.", layoutData.Data.Count));
            }
            return summary;
        }

        private void AddErrorToSummary(ref ErrorSummary summary, int row, int col, Field property, object val)
        {
            if (summary == null)
            {
                summary = new ErrorSummary();
                summary.Errors = new List<ErrorDescription>();
            }
            if (LOG.IsDebugEnabled)
            {
                LOG.Debug(string.Format("Error found: line[{0}], Column[{1}], Property[{2}], Description[{3}]", row,
                                        col, property.Name, val));
            }
            summary.Errors.Add(new ErrorDescription
            {
                Line = row,
                Column = col,
                Property = property,
                Severity = ErrorSeverity.Error,
                Type = ErrorType.Data,
                Description = string.Format("Formato de dato o valor invalido: [{0}]", val)
            });
        }

        private bool TryParse(Type type, string expression, out object val, Field field)
        {
            val = null;
            var modifiers = new ParameterModifier(2);
            modifiers[1] = true;
            try
            {
                var methodToInvoke = (from m in type.GetMethods(BindingFlags.Static | BindingFlags.Public)
                                      where m.Name == "TryParse" && m.GetParameters().Length == 2
                                      select m).SingleOrDefault();
                return (bool)methodToInvoke.Invoke(null, new[] { expression, val });
            }
            catch (Exception)
            {
                LOG.Error(string.Format("Error trying to parse field [{0}] value: [{1}] of type: [{2}]", 
                    field.Name, expression, type.Name));
                throw;
            }
        }
    }
}